﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Text;
using System.Threading.Tasks;
using StyleMVVM.Logging;
using StyleMVVM.View.Regions;
#if NETFX_CORE
using Windows.UI.Core;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;

#else
using System.Windows;
using System.Windows.Media;

#endif

namespace StyleMVVM.View
{
	public sealed class RegionNavigationHelper
	{
		private static readonly string supplemental = typeof(RegionNavigationHelper).FullName;
		private static readonly MethodInfo GoHomeHandlerMethod;
		private static readonly MethodInfo GoBackHandlerMethod;
		private static readonly MethodInfo GoForwardHandlerMethod;
		private static readonly MethodInfo NavigateHandlerMethod;
		private static readonly MethodInfo GenericConnectMethod;
		private static readonly MethodInfo GenericDisconnectMethod;

		static RegionNavigationHelper()
		{
			GoHomeHandlerMethod = typeof(RegionNavigationHelper).GetTypeInfo().GetDeclaredMethod("GoHomeHandler");

			GoBackHandlerMethod = typeof(RegionNavigationHelper).GetTypeInfo().GetDeclaredMethod("GoBackHandler");

			GoForwardHandlerMethod = typeof(RegionNavigationHelper).GetTypeInfo().GetDeclaredMethod("GoForwardHandler");

			NavigateHandlerMethod = typeof(RegionNavigationHelper).GetTypeInfo().GetDeclaredMethod("NavigateHandler");

			GenericConnectMethod =
				typeof(RegionNavigationHelper).GetTypeInfo().GetDeclaredMethod("GenericConnectHandler");

			GenericDisconnectMethod =
				typeof(RegionNavigationHelper).GetTypeInfo().GetDeclaredMethod("GenericDisconnectHandler");
		}

		public RegionNavigationHelper(DependencyObject obj)
		{
		}

		public static RegionNavigationHelper GetCurrentHelper(DependencyObject obj)
		{
			return (RegionNavigationHelper)obj.GetValue(InternalCurrentHelperProperty);
		}

		public static void SetCurrentHelper(DependencyObject obj, RegionNavigationHelper value)
		{
			obj.SetValue(InternalCurrentHelperProperty, value);
		}

		public static DependencyProperty CurrentHelperProperty
		{
			get { return InternalCurrentHelperProperty; }
		}

		// Using a DependencyProperty as the backing store for CurrentHelper.  This enables animation, styling, binding, etc...
		internal static readonly DependencyProperty InternalCurrentHelperProperty =
			DependencyProperty.RegisterAttached("CurrentHelper",
			                                    typeof(RegionNavigationHelper),
			                                    typeof(RegionNavigationHelper),
			                                    new PropertyMetadata(null));

		public static RegionNavigationHelper GetOrCreateCurrentHelper(DependencyObject obj)
		{
			RegionNavigationHelper returnValue = GetCurrentHelper(obj);

			if (returnValue == null)
			{
				returnValue = new RegionNavigationHelper(obj);

				SetCurrentHelper(obj, returnValue);
			}

			return returnValue;
		}

		public void ConnectGoBackEvent(DependencyObject control, string eventName)
		{
			ConnectEvent(control, eventName, GoBackHandlerMethod);
		}

		public void DisconnectGoBackEvent(DependencyObject control, string eventName)
		{
			DisconnectEvent(control, eventName, GoBackHandlerMethod);
		}

		public void ConnectGoForwardEvent(DependencyObject control, string eventName)
		{
			ConnectEvent(control, eventName, GoForwardHandlerMethod);
		}

		public void DisconnectGoForwardEvent(DependencyObject control, string eventName)
		{
			DisconnectEvent(control, eventName, GoBackHandlerMethod);
		}

		public void ConnectGoHomeEvent(DependencyObject control, string eventName)
		{
			ConnectEvent(control, eventName, GoHomeHandlerMethod);
		}

		public void DisconnectGoHomeEvent(DependencyObject control, string eventName)
		{
			DisconnectEvent(control, eventName, GoBackHandlerMethod);
		}

		public void ConnectNavigateEvent(DependencyObject control, string eventName)
		{
			ConnectEvent(control, eventName, NavigateHandlerMethod);
		}

		public void DisconnectNavigateEvent(DependencyObject control, string eventName)
		{
			DisconnectEvent(control, eventName, NavigateHandlerMethod);
		}

		protected void GoHomeHandler(object sender, object args)
		{
			IRegion region = null;

			string navigatingRegion = Region.GetNavigatingRegion(sender as DependencyObject);

			if (!string.IsNullOrEmpty(navigatingRegion))
			{
				IRegionManager manager =
					Bootstrapper.Instance.Container.LocateByType(typeof(IRegionManager)) as IRegionManager;

				if (manager != null)
				{
					region = manager.FindRegion(navigatingRegion);
				}
			}
			else
			{
				region = findMasterRegion(sender);
			}

			if (region != null)
			{
				region.GoHome();
			}
			else
			{
				Logger.Error("Could not find region on " + sender, supplemental);
			}
		}

		protected void GoBackHandler(object sender, object args)
		{
			IRegion region = null;
			string navigatingRegion = Region.GetNavigatingRegion(sender as DependencyObject);

			if (!string.IsNullOrEmpty(navigatingRegion))
			{
				IRegionManager manager =
					Bootstrapper.Instance.Container.LocateByType(typeof(IRegionManager)) as IRegionManager;

				if (manager != null)
				{
					region = manager.FindRegion(navigatingRegion);
				}
			}
			else
			{
				region = findMasterRegion(sender);
			}

			if (region != null)
			{
				region.GoBack();
			}
			else
			{
				Logger.Error("Could not find region on " + sender, supplemental);
			}
		}

		protected void GoForwardHandler(object sender, object args)
		{
			IRegion region = null;
			string navigatingRegion = Region.GetNavigatingRegion(sender as DependencyObject);

			if (!string.IsNullOrEmpty(navigatingRegion))
			{
				IRegionManager manager =
					Bootstrapper.Instance.Container.LocateByType(typeof(IRegionManager)) as IRegionManager;

				if (manager != null)
				{
					region = manager.FindRegion(navigatingRegion);
				}
			}
			else
			{
				region = findMasterRegion(sender);
			}

			if (region != null)
			{
				region.GoForward();
			}
			else
			{
				Logger.Error("Could not find region on " + sender, supplemental);
			}
		}

		protected void NavigateHandler(object sender, object args)
		{
			IRegion region = null;
			string navigatingRegion = Region.GetNavigatingRegion(sender as DependencyObject);
			string navigatingTo = Region.GetNavigateTo(sender as DependencyObject);

			if (!string.IsNullOrEmpty(navigatingRegion))
			{
				IRegionManager manager =
					Bootstrapper.Instance.Container.LocateByType(typeof(IRegionManager)) as IRegionManager;

				if (manager != null)
				{
					region = manager.FindRegion(navigatingRegion);
				}
			}
			else
			{
				region = findMasterRegion(sender);
			}

			if (region != null)
			{
				if (!string.IsNullOrEmpty(navigatingTo))
				{
					region.Navigate(navigatingTo);
				}
				else
				{
					Logger.Error("Missing NavigateTo Property on " + sender, supplemental);
				}
			}
			else
			{
				Logger.Error("Could not find region on " + sender, supplemental);
			}
		}

		protected IRegion findMasterRegion(object sender)
		{
			FrameworkElement frameworkElement = sender as FrameworkElement;

			if (frameworkElement != null)
			{
				IList<IRegion> currentRegions = BaseRegion.GetCurrentRegions(frameworkElement);

				if (currentRegions != null)
				{
					foreach (IRegion currentRegion in currentRegions)
					{
						if (currentRegion.IsContainerMaster)
						{
							return currentRegion;
						}
					}
				}
				else
				{
					return findMasterRegion(VisualTreeHelper.GetParent(frameworkElement));
				}
			}

			return null;
		}

		private void ConnectEvent(DependencyObject internalControl, string eventName, MethodInfo methodInfo)
		{
			EventInfo eventInfo = getEventInfo(internalControl.GetType(), eventName);

			if (eventInfo != null)
			{
				MethodInfo newEventType =
					GenericConnectMethod.MakeGenericMethod(eventInfo.EventHandlerType);

				newEventType.Invoke(this, new object[] { internalControl, eventInfo, this, methodInfo });
			}
			else
			{
				Logger.Error(
					string.Format("Could not find event {0} on type {1}", eventName, internalControl.GetType().FullName));
			}
		}

		private void DisconnectEvent(DependencyObject internalControl, string eventName, MethodInfo methodInfo)
		{
			EventInfo eventInfo = getEventInfo(internalControl.GetType(), eventName);

			if (eventInfo != null)
			{
				MethodInfo newEventType =
					GenericDisconnectMethod.MakeGenericMethod(eventInfo.EventHandlerType);

				newEventType.Invoke(this, new object[] { internalControl, eventInfo, this, methodInfo });
			}
			else
			{
				Logger.Error(
					string.Format("Could not find event {0} on type {1}", eventName, internalControl.GetType().FullName));
			}
		}

		private void GenericConnectHandler<T>(
			DependencyObject d, EventInfo eventInfo, object target, MethodInfo methodInfo) where T : class
		{
			try
			{
				T handlerDelegate = methodInfo.CreateDelegate(eventInfo.EventHandlerType, target) as T;

				Func<T, EventRegistrationToken> add =
					(a) => { return (EventRegistrationToken)eventInfo.AddMethod.Invoke(d, new object[] { a }); };

				Action<EventRegistrationToken> remove =
					(a) => eventInfo.RemoveMethod.Invoke(eventInfo, new object[] { a });

				WindowsRuntimeMarshal.AddEventHandler(add, remove, handlerDelegate);
			}
			catch (Exception exp)
			{
				Logger.Error("Exception thrown while setting event handler", typeof(RegionNavigationHelper).FullName, exp);
			}
		}

		private void GenericDisconnectHandler<T>(
			DependencyObject d, EventInfo eventInfo, object target, MethodInfo methodInfo) where T : class
		{
			try
			{
				T handlerDelegate = methodInfo.CreateDelegate(eventInfo.EventHandlerType, target) as T;

				Action<EventRegistrationToken> remove =
					(a) => eventInfo.RemoveMethod.Invoke(eventInfo, new object[] { a });

				WindowsRuntimeMarshal.RemoveEventHandler(remove, handlerDelegate);
			}
			catch (Exception exp)
			{
				Logger.Error("Exception thrown while disconnecting event handler",
				             typeof(RegionNavigationHelper).FullName,
				             exp);
			}
		}

		private EventInfo getEventInfo(Type type, string eventName)
		{
			EventInfo eventInfo = type.GetRuntimeEvent(eventName);

			if (eventInfo == null)
			{
				Type baseType = type.GetTypeInfo().BaseType;

				if (baseType != null)
				{
					return getEventInfo(type.GetTypeInfo().BaseType, eventName);
				}
			}

			return eventInfo;
		}
	}
}